/*=================================================
    File:	IMManager.c

	Module:	IMManager
	Author:	Mike	

=================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <spr.h>
#include <config.h>
#include <fdb.h>
#include <configFlashRecordId.h>
#include <debuglog.h>
#include <lang.h>
#include <callCtrl.h>
#include <dateTime.h>

#include "IMManager.h"

#define	 MAX_INBOX_SIZE		(configFlashRecordId_IM_INBOX_MAX-configFlashRecordId_IM_INBOX0)
#define	 MAX_OUTBOX_SIZE	(configFlashRecordId_IM_OUTBOX_MAX-configFlashRecordId_IM_OUTBOX0)
#define	 MAX_DRAFT_SIZE		(configFlashRecordId_IM_DRAFT_MAX-configFlashRecordId_IM_DRAFT0)

#if 1
#define dbgf	printf
#endif

static struct fdbWriteTask Inbox_fdbTaskRetries[MAX_INBOX_SIZE];
static struct fdbWriteTask Outbox_fdbTaskRetries[MAX_OUTBOX_SIZE];
static struct fdbWriteTask Draft_fdbTaskRetries[MAX_DRAFT_SIZE];

//by Mike
static bool g_bNewMessage = false;

typedef struct _IM_INBOX_FLASH
{
	bool isUsed;
	IM_INBOX  in;
} IM_INBOX_FLASH;

typedef struct _IM_OUTBOX_FLASH
{
	bool isUsed;
	unsigned	time; //save time
	IM_OUTBOX  out;
} IM_OUTBOX_FLASH;

typedef struct _IM_DRAFTBOX_FLASH
{
	bool isUsed;
	IM_DRAFTBOX  draft;
} IM_DRAFTBOX_FLASH;

static bool Inbox_RecordFlashWrite(int index, IM_INBOX_FLASH *record);
static bool Inbox_RecordFlashRead(int index, IM_INBOX_FLASH *record);

static bool Outbox_RecordFlashWrite(int index, IM_OUTBOX_FLASH *record);
static bool Outbox_RecordFlashRead(int index, IM_OUTBOX_FLASH *record);

static bool Draft_RecordFlashWrite(int index, IM_DRAFTBOX_FLASH *record);
static bool Draft_RecordFlashRead(int index, IM_DRAFTBOX_FLASH *record);

typedef struct
{
	IM_INBOX_FLASH  in_f[MAX_INBOX_SIZE];
	IM_OUTBOX_FLASH	out_f[MAX_OUTBOX_SIZE];
	IM_DRAFTBOX_FLASH	draft_f[MAX_DRAFT_SIZE];

	int in_used, out_used, draft_used;
	bool isInit;
} IM_BOXES;

static IM_BOXES	_imb = {};

bool  IM_InBox_GetSize(int * used, int * total)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(used && total)
	{
		*used = _imb.in_used;
		*total = MAX_INBOX_SIZE;
		return true;
	}
	return false;
}

int   IM_InBox_AddMsg(char * number, char * msg)
{
	//Better use lock, but we cannot dut to single thread of OS!
	//by Mike
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return IM_ERR_NOT_INIT;
	}

	//check if can add
	if(_imb.in_used>=MAX_INBOX_SIZE || fdbSafeFreeCheck())
	{
		dbgf("[IM] Inbox: no room for msg!\n");
		return IM_ERR_NO_ROOM;
	}

	if(number && *number 
		&& msg && *msg)
	{

		//before save, need filter the reduplicate message(s) with the last one
		//by Mike
		if(_imb.in_used>0)
		{
			//That maybe check time is better
			if(!_imb.in_f[_imb.in_used-1].in.bRead 
				&& strcmp(number, _imb.in_f[_imb.in_used-1].in.number)==0
				&& strcmp(msg, _imb.in_f[_imb.in_used-1].in.msg)==0 )
			{
				return IM_ERR_NOT_AVAIL; //ignore the message
			}
		}

		IM_INBOX_FLASH  in_f = {};
		//ok, save msg
		sprcpy(in_f.in.number, sizeof(in_f.in.number), number);
		sprcpy(in_f.in.msg, sizeof(in_f.in.msg), msg);

		//for datetime
		DateTime dtNow = {};
		dtNow = DateTimeNow();
		spr(in_f.in.date, sizeof(in_f.in.date), "%02u/%02u/%02u", dtNow.month, dtNow.mday, dtNow.year);
		spr(in_f.in.time, sizeof(in_f.in.time), "%u:%02u", dtNow.hour, dtNow.minute);

		//Ignore domain/port part
			
		//set flag
		in_f.isUsed = 1;
			
		//add to flash first
		if(!Inbox_RecordFlashWrite(_imb.in_used, &in_f))
		{
			return IM_ERR_ACCESS_FLASH;
		}

		//save
		memcpy(&_imb.in_f[_imb.in_used++], &in_f, sizeof(IM_INBOX_FLASH));
		dbgf("[IM] A new message comes!\n");
		//Notify UI, but how?
		//Here we just leave and let UI do update by itself.
		//by Mike

		//now, set the flag, ignore the last one
		if(_imb.in_used<MAX_INBOX_SIZE)
		{
			g_bNewMessage = true;
		}

		return _imb.in_used-1;
	}
	return IM_ERR_PARAM_INVALID;
}

/*
int   IM_InBox_AddMsg(char * number, char * domain, unsigned port, char * msg)
{
	return IM_ERR_NOT_AVAIL;
}
*/

bool  IM_InBox_GetMsgByIndex(int index, IM_INBOX * pMsg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.in_used && pMsg)
	{
		memcpy(pMsg, &_imb.in_f[index].in, sizeof(IM_INBOX));
		return true;
	}
	return false;
}

bool  IM_InBox_DeleteMsgByIndex(int index)
{
	//Better use lock, but we cannot dut to single thread of OS!
	//by Mike
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.in_used)
	{
		//move up to keep in order
		for(int i=index;i<_imb.in_used-1;i++)
		{
			memcpy(&_imb.in_f[i], &_imb.in_f[i+1], sizeof(IM_INBOX_FLASH));
			Inbox_RecordFlashWrite(i, &_imb.in_f[i]);
		}

		_imb.in_used--;
		_imb.in_f[_imb.in_used].isUsed = false;

		IM_INBOX_FLASH in_f = {};
		Inbox_RecordFlashWrite(_imb.in_used, &in_f);
		return true;

	}

	return false;
}

bool  IM_InBox_Cleanup()
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	for(int i=_imb.in_used;i>0;i--)
	{
		IM_InBox_DeleteMsgByIndex(i-1);
	}
	return _imb.in_used==0;
}

bool  IM_InBox_GetMsgNumberInfoByIndex(int index, char * number, int size)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.in_used && number && size>0)
	{
		sprcpy(number, size, _imb.in_f[index].in.number);
		return true;
	}
	return false;
}

bool  IM_InBox_GetMsgReadFlagByIndex(int index, bool * isRead)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.in_used && isRead)
	{
		*isRead =  _imb.in_f[index].in.bRead;
		return true;
	}
	return false;
}

bool  IM_InBox_SetMsgReadFlagByIndex(int index)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.in_used)
	{
		_imb.in_f[index].in.bRead = true;
		//save to flash
		if(!Inbox_RecordFlashWrite(index, &_imb.in_f[index]))
		{
			return false;
		}
		return true;
	}
	return false;
}

bool  IM_OutBox_GetSize(int * used, int * total)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(used && total)
	{
		*used = _imb.out_used;
		*total = MAX_OUTBOX_SIZE;
		return true;
	}
	return false;
}

static int   IM_OutBox_AddMsg(char * number, char * msg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return IM_ERR_NOT_INIT;
	}

	//check if can add
#if 0
	if(_imb.out_used>=MAX_OUTBOX_SIZE || fdbSafeFreeCheck())
	{
		dbgf("[IM] Outbox: no room for msg!\n");
		return IM_ERR_NO_ROOM;
	}
#else

	//Instead, we use a way to drop out the oldest...
	if(fdbSafeFreeCheck())
	{
		dbgf("[IM] Outbox: no room for msg!\n");
		return IM_ERR_NO_ROOM;
	}

	int drop = -1;
	if(_imb.out_used==MAX_OUTBOX_SIZE)
	{
		unsigned time = -1;
		for(int index=0;index<_imb.out_used;index++)
		{
			if(time>_imb.out_f[index].time)
			{
				time = _imb.out_f[index].time;
				drop = index;
			} 
		}
	}
#endif

	if(number && *number 
		&& msg && *msg)
	{
		IM_OUTBOX_FLASH  out_f = {};
		//ok, save msg
		sprcpy(out_f.out.number, sizeof(out_f.out.number), number);
		sprcpy(out_f.out.msg, sizeof(out_f.out.msg), msg);

		//for datetime
		DateTime dtNow = {};
		dtNow = DateTimeNow();
		spr(out_f.out.date, sizeof(out_f.out.date), "%02u/%02u/%02u", dtNow.month, dtNow.mday, dtNow.year);
		spr(out_f.out.time, sizeof(out_f.out.time), "%u:%02u", dtNow.hour, dtNow.minute);

		//ignore domain/port
			
		//set flag
		out_f.isUsed = 1;
		out_f.time = hwClockGet();
			
		//add to flash first
		if(!Outbox_RecordFlashWrite(drop<0?_imb.out_used:drop, &out_f))
		{
			return IM_ERR_ACCESS_FLASH;
		}

		//save
		if(drop<0)
		{
			memcpy(&_imb.out_f[_imb.out_used++], &out_f, sizeof(IM_OUTBOX_FLASH));
			return _imb.out_used-1;
		}
		else
		{
			//replace
			memcpy(&_imb.out_f[drop], &out_f, sizeof(IM_OUTBOX_FLASH));
			return drop;
		}
		
	}
	return IM_ERR_PARAM_INVALID;
	
}

/*
static int   IM_OutBox_AddMsg(char * number, char * domain, unsigned port, char * msg)
{
	return IM_ERR_NOT_AVAIL;
}
*/

bool  IM_OutBox_GetMsgByIndex(int index, IM_OUTBOX * pMsg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}
	
	if(index>=0 && index<_imb.out_used && pMsg)
	{
		memcpy(pMsg, &_imb.out_f[index].out, sizeof(IM_OUTBOX));
		return true;
	}
	return false;
}

bool  IM_OutBox_DeleteMsgByIndex(int index)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.out_used)
	{
		//move up to keep in order
		for(int i=index;i<_imb.out_used-1;i++)
		{
			memcpy(&_imb.out_f[i], &_imb.out_f[i+1], sizeof(IM_OUTBOX_FLASH));
			Outbox_RecordFlashWrite(i, &_imb.out_f[i]);
		}

		_imb.out_used--;
		_imb.out_f[_imb.out_used].isUsed = false;

		IM_OUTBOX_FLASH out_f = {};
		Outbox_RecordFlashWrite(_imb.out_used, &out_f);
		return true;

	}
	return false;
}

bool  IM_OutBox_Cleanup()
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	for(int i=_imb.out_used;i>0;i--)
	{
		IM_OutBox_DeleteMsgByIndex(i-1);
	}
	return _imb.out_used==0;
}

bool  IM_OutBox_GetMsgNumberInfoByIndex(int index, char * number, int size)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.out_used && number && size>0)
	{
		sprcpy(number, size, _imb.out_f[index].out.number);
		return true;
	}

	return false;
}


bool  IM_DraftBox_GetSize(int * used, int * total)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(used && total)
	{
		*used = _imb.draft_used;
		*total = MAX_DRAFT_SIZE;
		return true;
	}
	return false;
}

int   IM_DraftBox_AddMsg(char * msg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return IM_ERR_NOT_INIT;
	}

	//check if can add
	if(_imb.draft_used>=MAX_DRAFT_SIZE || fdbSafeFreeCheck())
	{
		dbgf("[IM] Draft: no room for msg!\n");
		return IM_ERR_NO_ROOM;
	}

	if(msg && *msg)
	{
		IM_DRAFTBOX_FLASH  draft_f = {};
		//ok, save msg
		sprcpy(draft_f.draft.msg, sizeof(draft_f.draft.msg), msg);
			
		//set flag
		draft_f.isUsed = 1;
			
		//add to flash first
		if(!Draft_RecordFlashWrite(_imb.draft_used, &draft_f))
		{
			return IM_ERR_ACCESS_FLASH;
		}

		//save
		memcpy(&_imb.draft_f[_imb.draft_used++], &draft_f, sizeof(IM_DRAFTBOX_FLASH));
		return _imb.draft_used-1;
		
	}
	return IM_ERR_PARAM_INVALID;

}

bool  IM_DraftBox_GetMsgByIndex(int index, IM_DRAFTBOX * pMsg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	if(index>=0 && index<_imb.draft_used && pMsg)
	{
		memcpy(pMsg, &_imb.draft_f[index].draft, sizeof(IM_DRAFTBOX));
		return true;
	}
	return false;
}

bool  IM_DraftBox_DeleteMsgByIndex(int index)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}
	
	//*******************
	//just replace it and del the last one
	//Need UI reload after call to keep synchro.
	//*******************
	if(index>=0 && index<_imb.draft_used)
	{
		memcpy(&_imb.draft_f[index], &_imb.draft_f[_imb.draft_used-1], sizeof(IM_DRAFTBOX));
		Draft_RecordFlashWrite(index, &_imb.draft_f[index]);

		_imb.draft_used--;
		memset(&_imb.draft_f[_imb.draft_used], 0, sizeof(IM_DRAFTBOX));
		Draft_RecordFlashWrite(_imb.draft_used, &_imb.draft_f[_imb.draft_used]);

		return true;
	}
	return false;
}

bool  IM_DraftBox_Cleanup()
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return false;
	}

	for(int index=_imb.draft_used-1;index>=0;index--)
	{
		IM_DraftBox_DeleteMsgByIndex(index);
	}
	return _imb.draft_used==0;
}

/////////////////////////////////////////////////
// Local op for flash r/w
// 
bool Inbox_RecordFlashWrite(int index, IM_INBOX_FLASH *record)
{
	if(index<0 || index>=MAX_INBOX_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_INBOX0 + index;
	if ( fdbRecordWriteRetry(
			&Inbox_fdbTaskRetries[index],
			FlashId,
			(char *)record,
			sizeof(IM_INBOX_FLASH)
			) )
	{
		dbgf("[IM] Inbox: Fail to write to flash!\n");
		return false;
	}
	return true;

}

bool Inbox_RecordFlashRead(int index, IM_INBOX_FLASH *record)
{
	unsigned char buf[sizeof(IM_INBOX_FLASH)] = {};

	if(index<0 || index>=MAX_INBOX_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_INBOX0 + index;

    if (fdbRecordRead(FlashId, buf, sizeof(IM_INBOX_FLASH)) != 0) 
    {
        return false;      
    }

	memcpy(record, buf, sizeof(IM_INBOX_FLASH));
	return true;
}

bool Outbox_RecordFlashWrite(int index, IM_OUTBOX_FLASH *record)
{
	if(index<0 || index>=MAX_OUTBOX_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_OUTBOX0 + index;
	if ( fdbRecordWriteRetry(
			&Outbox_fdbTaskRetries[index],
			FlashId,
			(char *)record,
			sizeof(IM_OUTBOX_FLASH)
			) )
	{
		return false;
	}
	return true;
}

bool Outbox_RecordFlashRead(int index, IM_OUTBOX_FLASH *record)
{
	unsigned char buf[sizeof(IM_OUTBOX_FLASH)] = {};

	if(index<0 || index>=MAX_OUTBOX_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_OUTBOX0 + index;

    if (fdbRecordRead(FlashId, buf, sizeof(IM_OUTBOX_FLASH)) != 0) 
    {
        return false;      
    }

	memcpy(record, buf, sizeof(IM_OUTBOX_FLASH));
	
	return true;
}

bool Draft_RecordFlashWrite(int index, IM_DRAFTBOX_FLASH *record)
{
	if(index<0 || index>=MAX_DRAFT_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_DRAFT0 + index;
	if ( fdbRecordWriteRetry(
			&Draft_fdbTaskRetries[index],
			FlashId,
			(char *)record,
			sizeof(IM_DRAFTBOX_FLASH)
			) )
	{
		dbgf("[Draft] Fail to write to flash!\n");
		return false;
	}
	return true;
}

bool Draft_RecordFlashRead(int index, IM_DRAFTBOX_FLASH *record)
{
	unsigned char buf[sizeof(IM_DRAFTBOX_FLASH)] = {};

	if(index<0 || index>=MAX_DRAFT_SIZE || !record)
	{
		return false;
	}

	unsigned FlashId = configFlashRecordId_IM_DRAFT0 + index;

    if (fdbRecordRead(FlashId, buf, sizeof(IM_DRAFTBOX_FLASH)) != 0) 
    {
        return false;
    }

	memcpy(record, buf, sizeof(IM_DRAFTBOX_FLASH));
	
	return true;
}

static void _LoadInBoxInfo()
{
	int i;
	for(i=0;i<MAX_INBOX_SIZE;i++)
	{
		IM_INBOX_FLASH in = {};
		
		if(!Inbox_RecordFlashRead(i, &in) ||
			in.isUsed == 0)
		{
			break;
		}
		memcpy(&_imb.in_f[i], &in, sizeof(IM_INBOX_FLASH));
	}
	//
	_imb.in_used = i;
}

static void _LoadOutBoxInfo()
{
	int i;
	for(i=0;i<MAX_OUTBOX_SIZE;i++)
	{
		IM_OUTBOX_FLASH out = {};
		
		if(!Outbox_RecordFlashRead(i, &out) ||
			out.isUsed == 0)
		{
			break;
		}
		memcpy(&_imb.out_f[i], &out, sizeof(IM_OUTBOX_FLASH));
	}
	//
	_imb.out_used = i;
}

static void _LoadDraftInfo()
{
	int i;
	for(i=0;i<MAX_DRAFT_SIZE;i++)
	{
		IM_DRAFTBOX_FLASH draft = {};
		
		if(!Draft_RecordFlashRead(i, &draft) ||
			draft.isUsed == 0)
		{
			break;
		}
		memcpy(&_imb.draft_f[i], &draft, sizeof(IM_DRAFTBOX_FLASH));
	}
	//
	_imb.draft_used = i;
}

////////////////////////////////////////////
// Intialize Func.
// Called by outer when init
//
int IMManager_Initialize()
{
	if(!_imb.isInit)
	{
		//load all from flash
		//It may take some time...
		_LoadInBoxInfo();
		_LoadOutBoxInfo();
		_LoadDraftInfo();

		_imb.isInit = true;
	}

	return 0;
}

int   IMManager_SendOutMsg(char * number, char * msg)
{
	if(!_imb.isInit)
		return  IM_ERR_NOT_INIT;

	if(!IMManager_CanSend())
		return IM_ERR_NOT_AVAIL;

	if(callCtrlSendIM(number, msg))
	{
		//Try to save to outbox
		return IM_OutBox_AddMsg(number, msg);
	}
	return IM_ERR_OP_FAIL;
}

/*int   IMManager_SendOutMsg(char * number, char * domain, unsigned port, char * msg)
{
	return IM_ERR_NOT_AVAIL;
}
*/

bool IMManager_CanSend()
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Manager: Not init yet!\n");
		return  false;
	}
	return callCtrlCansendIM();
}

//
//for draft update issue
//
int   IM_DraftBox_UpdateMsg(int index, char * msg)
{
	if(!_imb.isInit)
	{
		dbgf("[IM] Not init yet!\n");
		return IM_ERR_NOT_INIT;
	}

	//check if can add
	if(index<0 || index>=_imb.draft_used)
	{
		dbgf("[IM] Draft: invalid index parameter!\n");
		return IM_ERR_PARAM_INVALID;
	}

	if(msg && *msg)
	{
		IM_DRAFTBOX_FLASH  draft_f = {};
		//ok, save msg
		sprcpy(draft_f.draft.msg, sizeof(draft_f.draft.msg), msg);
			
		//set flag
		draft_f.isUsed = 1;
			
		//write to flash first
		if(!Draft_RecordFlashWrite(index, &draft_f))
		{
			return IM_ERR_ACCESS_FLASH;
		}

		//save
		memcpy(&_imb.draft_f[index], &draft_f, sizeof(IM_DRAFTBOX_FLASH));
		return index;
		
	}
	return IM_ERR_PARAM_INVALID;
}

//for new message issue
//by Mike
bool  IM_InBox_GetNewMsgFlag()
{
	return g_bNewMessage;
}

void  IM_InBox_ClearNewMsgFlag()
{
	g_bNewMessage = false;
}

